/* 静态资源-TS 检测策略 */
const ts = require('typescript');
const CONSTS = require('@lib/consts/index');
const Report = require('@lib/utils/report');
const Fs = require('@lib/utils/fs');
const SrJsStrategy = require('./srJs');
const Js = require('@lib/utils/js');
const {
  isValuableArray,
  mergeArrayByField
} = require('@lib/utils/tools');

// 需要写入报告文件的数据
const outputList = [];

class SrTs {
  /**
   * 执行策略的函数
   * @param {*} tsList ts文件列表
   * @param {*} extra 额外参数
   * @param {*} cb 回调函数
   */
  static async execute(tsList = [], extra, cb) {
    if (!isValuableArray(tsList)) {
      cb && cb();
      return;
    }
    // 1.按照文件遍历
    for (const item of tsList) {
      const { filePath } = item;
      const tsContent = Fs.readFileSync(filePath);
      // 2.策略开始
      // ts策略开始
      // 解析 TypeScript 代码为 AST
      const tsAst = ts.createSourceFile(filePath, tsContent, ts.ScriptTarget.Latest, true);
      SrTs.detectLongTypes(item, tsAst, outputList);
      SrTs.detectDeepNesting(item, tsAst, outputList);
      SrTs.detectTypesComplexity(item, tsAst, outputList);
      // 复用js策略开始
      // TypeScript算是JavaScript的另一种表现形式，所以可以复用JavaScript检测策略
      // 以下策略都是复用JavaScript检测策略
      SrJsStrategy.detectFreqDOMOpera(item, tsContent, outputList);
      SrJsStrategy.detectLoopAndIterations(item, tsContent, outputList);
      SrJsStrategy.detectEventListener(item, tsContent, outputList);
      SrJsStrategy.detectImportFrom(item, tsContent, outputList);
      // 先获取js AST，再执行策略
      const jsAst = Js.getAstByJsCode(tsContent);
      SrJsStrategy.detectEvalUsage(item, jsAst, outputList);
      SrJsStrategy.detectAnimationCode(item, jsAst, outputList);
      SrJsStrategy.detectCommonJS(item, jsAst, outputList);
    }
    // 3.整合outputList相同title项
    const newOutputList = mergeArrayByField(outputList, 'title', 'data');
    // 4.输出数据到报告目录下的临时文件
    if (!newOutputList.length) {
      cb && cb();
      return;
    }
    const { index } = extra;
    Report.outputTempFile(
      newOutputList,
      '静态资源-TypeScript',
      `${index}sr-ts-${CONSTS.REPORT_TEMP_FILENAME}`,
      () => {
        cb && cb();
      }
    );
  }

  /**
   * 策略1：检测类型定义名称长度是否过长，超过阈值则给出谨慎使用的提示
   * @param {*} tsItem ts列表每一项
   * @param {*} tsAst TypeScript 代码解析出来的抽象语法树
   * @param {*} outputList 报告文件的数据列表
   */
  static detectLongTypes(tsItem, tsAst, outputList = []) {
    const limit = CONSTS.MAX_TYPESCRIPT_NAME_LENGTH;
    const list = [];
    // 遍历AST函数，查找是否存在类型定义名称长度过长的情况
    function visitNode(node) {
      if (node.kind === ts.SyntaxKind.TypeReference) {
        const typeName = node.typeName.text;
        if (typeName.length > limit) {
          list.push(typeName);
        }
      }
      // 遍历子节点
      ts.forEachChild(node, visitNode);
    }
    visitNode(tsAst);
    if (isValuableArray(list)) {
      Report.formatAndpushReportData(
        [{
          fileName: tsItem.fileName,
          filePath: tsItem.filePath,
          warnMsg: `存在${list.length}处类型定义名称长度超过${limit}个字符，类型名称如下：<br>${list.join('<br>')}`
        }],
        '【!谨慎使用】TypeScript类型定义名称过长',
        outputList
      );
    }
  }

  /**
   * 策略2：检测类型嵌套深度是否过深，超过阈值则给出谨慎使用的提示
   * @param {*} tsItem ts列表每一项
   * @param {*} tsAst TypeScript 代码解析出来的抽象语法树
   * @param {*} outputList 报告文件的数据列表
   */
  static detectDeepNesting(tsItem, tsAst, outputList = []) {
    const limit = CONSTS.MAX_TYPESCRIPT_NESTING_DEPTH;
    let maxDepth = 0; // 最大深度
    // 遍历 AST，检测类型定义嵌套深度
    function visitNode(node, currentDepth) {
      // 对象类型字面量的节点类型，才递增
      if (node.kind === ts.SyntaxKind.TypeLiteral) {
        currentDepth += 1;
      }
      if (currentDepth > limit) {
        maxDepth = Math.max(maxDepth, currentDepth);
      }
      ts.forEachChild(node, (childNode) => {
        visitNode(childNode, currentDepth);
      });
    }
    visitNode(tsAst, 0);
    if (maxDepth > limit) {
      Report.formatAndpushReportData(
        [{
          fileName: tsItem.fileName,
          filePath: tsItem.filePath,
          warnMsg: `嵌套深度为${maxDepth}层`
        }],
        `【!谨慎使用】类型嵌套深度超过${limit}层`,
        outputList
      );
    }
  }

  /**
   * 策略3：检测类型定义的复杂度，超过阈值则给出谨慎使用的提示
   * @param {*} tsItem ts列表每一项
   * @param {*} tsAst TypeScript 代码解析出来的抽象语法树
   * @param {*} outputList 报告文件的数据列表
   */
  static detectTypesComplexity(tsItem, tsAst, outputList = []) {
    const limit = CONSTS.MAX_TYPESCRIPT_TYPE_COMPLEXITY;
    let maxTypeComplexity = 0; // 最大复杂度
    // 遍历 AST，检测类型定义复杂性
    function calculateComplexity(node) {
      if (node.kind === ts.SyntaxKind.TypeAliasDeclaration) {
        const complexity = SrTs._getTypeComplexity(node);
        // console.log(`类型定义复杂度: ${complexity}`);
        if (complexity > limit) {
          maxTypeComplexity = Math.max(maxTypeComplexity, complexity);
        }
      }
      ts.forEachChild(node, calculateComplexity);
    }
    calculateComplexity(tsAst);
    if (maxTypeComplexity > limit) {
      Report.formatAndpushReportData(
        [{
          fileName: tsItem.fileName,
          filePath: tsItem.filePath,
          warnMsg: `类型定义复杂度为${maxTypeComplexity}`
        }],
        `【!谨慎使用】存在复杂度超过${limit}的类型定义`,
        outputList
      );
    }
  }

  /**
   * 根据类型定义规则数量和权重计算复杂度
   * @param {*} node 类型定义节点
   * @returns 类型定义复杂度
   */
  static _getTypeComplexity(node) {
    // ts.SyntaxKind.TypeAliasDeclaration 表示类型别名声明
    if (node.kind !== ts.SyntaxKind.TypeAliasDeclaration) {
      return 0;
    }
    let complexity = 0;
    // 复杂性权重
    // genericParams: 1; typeWidth: 1; unionOrIntersection: 5
    const weights = [1, 1, 5];

    // 复杂度规则
    const values = [];
    // 规则1.泛型参数数量
    const genericParamsCount = node.typeParameters ? node.typeParameters.length : 0;
    // console.log(`genericParamsCount: ${genericParamsCount}`);
    values.push(genericParamsCount);
    // 规则2.类型节点宽度，即成员数量
    const typeNode = node.type;
    const typeWidth = SrTs._getTypeWidth(typeNode);
    // console.log(`typeWidth: ${typeWidth}`);
    values.push(typeWidth);
    // 规则3.类型是否包含联合类型或交叉类型
    const hasUnionOrIntersection = typeNode && (
      // 联合类型
      typeNode.kind === ts.SyntaxKind.UnionType ||
      // 交叉类型
      typeNode.kind === ts.SyntaxKind.IntersectionType ||
      // 类型为字面量类型时检查其中的成员是否包含联合类型或交叉类型
      (typeNode.kind === ts.SyntaxKind.TypeLiteral &&
        (typeNode.members.some(member =>
          member.kind === ts.SyntaxKind.UnionType ||
          member.kind === ts.SyntaxKind.IntersectionType
        ))
      )
    );
    // console.log(`hasUnionOrIntersection: ${hasUnionOrIntersection}`);
    values.push(hasUnionOrIntersection ? 1 : 0);
    // console.log(values);
    // 继续新增其他规则...

    // 计算复杂度
    complexity = values.reduce((acc, cur, idx) => acc + cur * weights[idx], 0);
    return complexity;
  }

  /**
   * 获取类型节点的宽度，即成员数量
   * @param {*} typeNode  TypeScript AST 中的类型节点
   * @returns 成员数量
   */
  static _getTypeWidth(typeNode) {
    // 如果是联合类型，获取每个联合的成员数量
    if (typeNode && typeNode.kind === ts.SyntaxKind.UnionType) {
      const unionTypeNode = typeNode;
      const typeWidths = unionTypeNode.types.map(SrTs._getTypeWidth);
      const maxTypeWidth = Math.max(...typeWidths);
      return maxTypeWidth;
    }
    // 如果是交叉类型，获取每个交叉的成员数量
    if (typeNode && typeNode.kind === ts.SyntaxKind.IntersectionType) {
      const intersectionTypeNode = typeNode;
      const typeWidths = intersectionTypeNode.types.map(SrTs._getTypeWidth);
      const sumTypeWidth = typeWidths.reduce((acc, cur) => acc + cur, 0);
      return sumTypeWidth;
    }
    // 如果是类型字面量，直接获取成员数量
    if (typeNode && typeNode.kind === ts.SyntaxKind.TypeLiteral) {
      const typeLiteralNode = typeNode;
      return typeLiteralNode.members ? typeLiteralNode.members.length : 0;
    }
    return 0;
  }
}
module.exports = SrTs;
